package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annotations.*;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LgDispatherServlet extends HttpServlet {
    private Properties properties = new Properties();
    private List<String> classNames = new ArrayList<>();
    private Map<String,Object> ioc = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();



    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);
        // 2 扫描包，扫描注解
        doScan(properties.getProperty("scanPackage"));
        // 3 ioc初始化bean对象
        doInstance();
        // 4实现依赖注入
        doAutoWired();
        // 5 构造HandlerMapping，url和method的映射
        initHandleMapping();

        System.out.println(".......lagou mvc 初始化完成......");
        // 6 等待请求进入，处理

    }


    private void initHandleMapping() {
        if (!ioc.isEmpty()) {
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                Class<?> aClass = entry.getValue().getClass();
                if (aClass.isAnnotationPresent(LgController.class)){
                    String beasUrl = "";
                    if (aClass.isAnnotationPresent(LgRequestMapping.class)){
                        LgRequestMapping annotation = aClass.getAnnotation(LgRequestMapping.class);
                        beasUrl = annotation.value();
                    }
                    List<String> securityValue = new ArrayList<>();
                    if (aClass.isAnnotationPresent(Security.class)){
                        Security security = aClass.getAnnotation(Security.class);
                        String[] value = security.value();
                        securityValue = Arrays.asList(value);
                    }
                    Method[] methods = aClass.getMethods();
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(Security.class)){
                            Security annotation = method.getAnnotation(Security.class);
                            String[] methodValue = annotation.value();
                            securityValue = Arrays.asList(methodValue);
                        }
                        if (method.isAnnotationPresent(LgRequestMapping.class)){
                            LgRequestMapping annotation = method.getAnnotation(LgRequestMapping.class);
                            String methodUrl = beasUrl + annotation.value();
                        //    handlerMapping.put(beasUrl,method);
                            Handler handler = new Handler(entry.getValue(),method, Pattern.compile(methodUrl), securityValue);
                            Parameter[] parameters = method.getParameters();
                            for (int i=0; i< parameters.length;i++) {
                                Parameter parameter = parameters[i];
                                if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class){
                                    handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),i);
                                }else {
                                    handler.getParamIndexMapping().put(parameter.getName(),i);
                                }
                            }
                            handlerMapping.add(handler);
                        }

                    }

                }
            }
        }

    }

    private void doAutoWired() {
        try {
            if (!ioc.isEmpty()) {
                for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                    String id = entry.getKey();
                    Object obj = entry.getValue();
                    Class<?> aClass = obj.getClass();
                    Field[] declaredFields = aClass.getDeclaredFields();
                    for (Field declaredField : declaredFields) {
                        if (declaredField.isAnnotationPresent(LgAutoWired.class)) {

                            LgAutoWired annotation = declaredField.getAnnotation(LgAutoWired.class);
                            String beanName = annotation.value();
                            if("".equals(beanName.trim())) {
                                beanName = declaredField.getType().getName();
                            }
                            Object o = ioc.get(beanName);
                            if (o!=null) {
                                declaredField.setAccessible(true);
                                declaredField.set(obj, o);
                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void doInstance() {
        if (classNames.size() == 0)
            return;
        try {
            for (String className:classNames){
                Class<?> aClass = Class.forName(className);
                if(aClass.isAnnotationPresent(LgController.class)){
                    String simpleName = aClass.getSimpleName();
                    String id = lowrFirst(simpleName);
                    Object o = aClass.newInstance();
                    ioc.put(id,o);
                }else if (aClass.isAnnotationPresent(LgService.class)){
                    LgService annotation = aClass.getAnnotation(LgService.class);
                    String beanName = annotation.value();
                    if (!"".equals(beanName)){
                        ioc.put(beanName,aClass.newInstance());
                    }else {
                        String simpleName = aClass.getSimpleName();
                        beanName = lowrFirst(simpleName);
                        ioc.put(beanName,aClass.newInstance());
                    }

                    //接口
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        //接口全限定类名作为id
                        ioc.put(anInterface.getName(),aClass.newInstance());
                    }
                }else {
                    continue;
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public String lowrFirst(String str){
        char[] chars = str.toCharArray();
        if('A' <= chars[0] && chars[0] <= 'Z'){
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file:files){
            if (file.isDirectory()){
                doScan(scanPackage+"."+file.getName());
            }else if(file.getName().endsWith(".class")){
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }

    }

    private void doLoadConfig(String contextConfigLocation){
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理请求
        Handler handler = getHandler(req);
        if (handler==null){
            resp.getWriter().write("404 not found");
            return;
        }
        //
        if (!doSecurity(req, handler)){
            resp.setContentType("text/html;charset=utf-8");
            resp.getWriter().write("HTTP ERROR 403, 没有访问权限");
            return;
        }
        Method method = handler.getMethod();
        Object controller = handler.getController();
        //传入args数组长度
        Class<?>[] parameterTypes = method.getParameterTypes();
        //根据数组长度创建新的数组，反射用
        Object[] paramValue = new Object[parameterTypes.length];
        Map<String, String[]> parameterMap = req.getParameterMap();

        for (Map.Entry<String,String[]> param : parameterMap.entrySet()){
            //将集合或数组以某拼接符拼接到一起形成新的字符串 name=1&name=2   name [1,2]  ==> 1,2
            String value = StringUtils.join(param.getValue(), ",");
            //填充参数
            if (handler.getParamIndexMapping().containsKey(param.getKey())){
                Integer index = handler.getParamIndexMapping().get(param.getKey());
                paramValue[index] = value;
            }
        }
        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paramValue[responseIndex] = resp;
        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paramValue[requestIndex] = req;

        try {
            method.invoke(controller,paramValue);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

    private Handler getHandler(HttpServletRequest req){
        if (!handlerMapping.isEmpty()) {
            String url = req.getRequestURI();
            for (Handler handler : handlerMapping) {
                Matcher matcher = handler.getPattern().matcher(url);
                if (matcher.matches()) {
                    return handler;
                }
            }
        }
        return null;
    }
    private boolean doSecurity(HttpServletRequest req, Handler handler){
        List<String> securityValue = handler.getSecurityValue();
        if (securityValue == null || securityValue.size() == 0){
            return true;
        }else {
            String parameter = req.getParameter("username");
            if (StringUtils.isEmpty(parameter)){
                return false;
            }else if(securityValue.contains(parameter)){
                return true;
            }else {
                return false;
            }
        }
    }


    /*private boolean doSecurity(HttpServletRequest req, Handler handler){
        boolean flag = false;
        String parameter = req.getParameter("username");

        Method method = handler.getMethod();
        if (method.isAnnotationPresent(Security.class)){
            if (StringUtils.isEmpty(parameter)){
                return false;
            }
            Security annotation = method.getAnnotation(Security.class);
            String[] methodValue = annotation.value();
            List<String> list = Arrays.asList(methodValue);
            if(list.contains(parameter)){
                flag = true;
            }
            return flag;
        }
        Object controller = handler.getController();
        Class<?> aClass = controller.getClass();
        if (aClass.isAnnotationPresent(Security.class)){
            if (StringUtils.isEmpty(parameter)){
                return false;
            }
            Security annotation = aClass.getAnnotation(Security.class);
            String[] methodValue = annotation.value();
            List<String> list = Arrays.asList(methodValue);
            if(list.contains(parameter)){
                flag = true;
            }
            return flag;
        }
        return true;
    }*/
}
